#!/usr/bin/env python
#
# Copyright 2013 The Imaging Source Europe GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from gi.repository import Gtk, GObject
import sys
import subprocess
import threading
from time import sleep


class CameraMode:
    """
    Enum for modes
    """
    UVC, PROPRIETARY = range(2)


class Workflow:
    """
    Enum for the different workflows the assistant can run
    """
    MODE, ALL = range(2)

APPLICATION_NAME = "TIS USB FirmwareUploader"
EUVCCAM_NAME = "./euvccam-fw"
EUVCCAM = EUVCCAM_NAME


def search_euvccam():
    """
    Searches for the executable and kills application when not found
    """
    import os

    def is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(EUVCCAM_NAME)
    if fpath:
        if is_exe(EUVCCAM_NAME):
            EUVCCAM = EUVCCAM_NAME
            return True
        else:
            for path in os.environ["PATH"].split(os.pathsep):
                path = path.strip('"')
                exe_file = os.path.join(path, EUVCCAM_NAME)
                if is_exe(exe_file):
                    global EUVCCAM
                    EUVCCAM = exe_file
                    return True

    return False


class Camera:
    """
    Simple camera representation describing the current
    values held by the physical camera
    """
    def __init__(self, _productstring=None, _mode=None, _serial=None,
                 _pid=None, _flags=None):
        self.productstring = _productstring
        self.mode = _mode
        self.serial = _serial
        self.pid = _pid
        self.flags = _flags


class Assistant:
    def __init__(self):

        self.workflow = Workflow.ALL
        self.change_mode = True
        self.change_firmware = False
        self.selected_mode = CameraMode.PROPRIETARY
        self.selected_camera = None
        self.cameras = self.retrieve_cameras()
        self.assistant = Gtk.Assistant()
        self.assistant.set_title(APPLICATION_NAME)
        self.assistant.set_size_request(500, 400)
        self.assistant.connect("apply", self.button_pressed, "Apply")
        self.assistant.connect("cancel", self.button_pressed, "Cancel")
        self.assistant.connect("close", self.button_pressed, "Close")
        self.assistant.set_forward_page_func(self.pageforward, None)

        # gtk assistants are set up statically i.e. everypage is created
        # and then the whole assistant is shown
        # here basic infrmation are defined to allow this setup.
        # pageforward should be used to define the correct values at runtime
        self.stop_progressbar = threading.Event()
        self.camera_is_updated = False
        self.confirmation_fw_label = None
        self.confirmation_mode_label = None
        self.result_label = None

    def start(self):
        """
        sets up all pages and initializes the assistant
        """
        self.create_welcome_page()
        self.create_camera_page()
        self.create_firmware_page()
        self.create_mode_page()
        self.create_confirmation_page()
        self.create_progression_page()
        self.create_last_page()

        self.assistant.show_all()

    def pageforward(self, page, data=None):
        """
        """
        page_number = self.assistant.get_current_page()

        if page == 1 and self.workflow == Workflow.MODE:
            return page + 2
        elif page == 3:
            self.update_confirmation_page()
        elif page == 5 and page_number == 5:
            if not self.camera_is_updated:
                # self.assistant.commit()

                self.bar_thr = threading.Thread(target=self.update_scrollbar,
                                                args=(self.stop_progressbar,))
                self.bar_thr.start()
                self.work_thr = threading.Thread(target=self.update_camera)
                self.work_thr.start()
                self.camera_is_updated = True
                if self.status == "SUCCESS":
                    self.result_label.set_text("Your camera has successfully been updated. "
                                               + "Please reconnected your camera "
                                               + "to assure full functionality.")
                else:
                    self.result_label.set_text("Something went wrong. "
                                               + "Please reconnect and try again.")
        # default behaviour
        return page+1

    def update_scrollbar(self, stop):
        """
        """
        prog = 0.1
        while(not stop.is_set()):
            self.progressbar.set_fraction(prog)
            prog += 0.1
            sleep(1)

    def update_camera(self):
        self.status = "SUCCESS"
        if self.change_firmware:
            result_fw = self.write_firmware()
            if not result_fw:
                self.status = "FAILED"
        if self.change_mode:
            result_mode = self.toggle_camera_mode(self.selected_mode)
            if not result_mode:
                self.status = "FAILED"

        self.stop_progressbar.set()
        page_number = self.assistant.get_current_page()
        current_page = self.assistant.get_nth_page(page_number)
        self.progressbar.set_fraction(1.0)
        self.assistant.set_page_complete(current_page, True)

    def get_buttons_hbox(self, assistant):
        # temporarily add a widget to the action area and get its parent
        label = Gtk.Label('')
        assistant.add_action_widget(label)
        hbox = label.get_parent()
        hbox.remove(label)
        return hbox

    def create_welcome_page(self):
        """
        First page
        Inform user about options
        """
        vbox = Gtk.VBox()
        vbox.set_border_width(5)

        self.assistant.append_page(vbox)
        self.assistant.set_page_title(vbox, "Welcome")
        self.assistant.set_page_type(vbox, Gtk.AssistantPageType.INTRO)
        label = Gtk.Label("This is the Imaging Source Firmware Updater for USB"
                          + " Cameras.\nIt will guide you through "
                          + "the configuration of your Camera.")
        label.set_line_wrap(True)
        vbox.pack_start(label, True, True, 0)
        radio_change_mode = \
            Gtk.RadioButton.new_with_label_from_widget(None,
                                                       "Toggle between Linux and Windows mode.")
        radio_change_mode.connect("clicked",
                                  self.workflow_selection,
                                  Workflow.MODE)
        vbox.add(radio_change_mode)

        radio_change_all = \
            Gtk.RadioButton.new_with_label_from_widget(radio_change_mode,
                                                       "Load Firmware and set mode")
        radio_change_all.connect("clicked",
                                 self.workflow_selection,
                                 Workflow.ALL)
        radio_change_all.set_active(True)
        vbox.add(radio_change_all)

        self.assistant.set_page_complete(vbox, True)

    def create_camera_page(self):
        """
        List available cameras and memories selected one
        """
        vbox = Gtk.VBox()
        vbox.set_border_width(5)
        self.assistant.append_page(vbox)
        self.assistant.set_page_title(vbox, "Select camera")
        self.assistant.set_page_type(vbox, Gtk.AssistantPageType.CONTENT)

        label = Gtk.Label("Please select the camera you want to update.\n")
        label.set_line_wrap(True)
        vbox.pack_start(label, False, False, 0)

        scrolled_window = Gtk.ScrolledWindow()
        scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
                                   Gtk.PolicyType.AUTOMATIC)
        vbox.pack_start(scrolled_window, True, True, 0)
        scrolled_window.show()

        columns = ["Camera Type", "Serial"]
        cam_list = Gtk.ListStore(str, str)

        for c in self.cameras:
            cam_list.append([c.productstring, c.serial])
        cam_view = Gtk.TreeView(model=cam_list)

        for i in range(len(columns)):
            cell = Gtk.CellRendererText()
            cell.props.weight_set = True
            col = Gtk.TreeViewColumn(columns[i], cell, text=i)
            cam_view.append_column(col)

        cam_selection = cam_view.get_selection()
        cam_selection.connect("changed", self.camera_selected)
        scrolled_window.add(cam_view)
        self.assistant.set_page_complete(vbox, False)

    def create_firmware_page(self):
        """
        Ask use if firmware shall be automatically applied
        Offer manual firmware selection
        """
        vbox = Gtk.VBox()
        vbox.set_border_width(5)
        self.assistant.append_page(vbox)
        self.assistant.set_page_title(vbox, "Select Firmware")
        self.assistant.set_page_type(vbox, Gtk.AssistantPageType.CONTENT)
        label = Gtk.Label("Select the firmware.")
        label.set_line_wrap(True)
        vbox.pack_start(label, True, True, 0)

        radio_auto = \
            Gtk.RadioButton.new_with_label_from_widget(None,
                                                       "Automatic Firmware Selection")
        radio_auto.connect("clicked", self.fw_selection, "automatic")
        vbox.add(radio_auto)

        radio_manual = \
            Gtk.RadioButton.new_with_label_from_widget(radio_auto,
                                                       "Manual Firmware Selection")
        radio_manual.connect("clicked", self.fw_selection, "manual")
        vbox.add(radio_manual)
        hbox = Gtk.HBox()
        self.entry = Gtk.Entry()
        self.entry.set_sensitive(False)

        hbox.pack_start(self.entry, True, True, 0)
        self.file_button = Gtk.Button("Open")
        self.file_button.connect("clicked", self.show_file_dialog)
        self.file_button.set_sensitive(False)
        hbox.pack_start(self.file_button, False, True, 0)

        vbox.pack_start(hbox, False, True, 0)
        self.assistant.set_page_complete(vbox, True)

    def create_mode_page(self):
        """
        Ask what mode setting the camera shall have
        """
        # Mode selection
        vbox = Gtk.VBox()
        vbox.set_border_width(5)
        self.assistant.append_page(vbox)
        self.assistant.set_page_title(vbox, "Select Mode")
        self.assistant.set_page_type(vbox, Gtk.AssistantPageType.CONTENT)
        label = Gtk.Label("Select the mode the camera shall run."
                          + "This will assure the correct driver will be loaded.")
        label.set_line_wrap(True)
        vbox.pack_start(label, True, True, 0)

        radio_windows = \
            Gtk.RadioButton.new_with_label_from_widget(None,
                                                       "Windows/Proprietary")
        radio_windows.connect("clicked",
                              self.mode_selection,
                              CameraMode.PROPRIETARY)
        vbox.add(radio_windows)

        radio_linux = Gtk.RadioButton.new_with_label_from_widget(radio_windows,
                                                                 "Linux/UVC")
        radio_linux.connect("clicked",
                            self.mode_selection,
                            CameraMode.UVC)
        vbox.add(radio_linux)
        radio_windows.set_active(True)

        self.assistant.set_page_complete(vbox, True)

    def create_confirmation_page(self):
        """
        """
        vbox = Gtk.VBox()
        vbox.set_border_width(5)
        self.assistant.append_page(vbox)
        self.assistant.set_page_title(vbox, "Confirm settings")
        self.assistant.set_page_type(vbox, Gtk.AssistantPageType.CONFIRM)
        label = Gtk.Label("An overview over the changes you want: ")
        label.set_line_wrap(True)
        vbox.pack_start(label, True, True, 0)

        self.confirmation_fw_label = Gtk.Label("")
        self.confirmation_fw_label.set_line_wrap(True)
        vbox.add(self.confirmation_fw_label)

        self.confirmation_mode_label = Gtk.Label("")
        self.confirmation_mode_label.set_line_wrap(True)
        vbox.add(self.confirmation_mode_label)

        self.assistant.set_page_complete(vbox, True)

    def update_confirmation_page(self):
        """
        """
        if self.change_firmware:
            self.confirmation_fw_label.set_text("Firmware will be updated to"
                                                + " most recent version.")
        else:
            self.confirmation_fw_label.set_text("")

        if self.change_mode:
            if self.selected_mode == CameraMode.UVC:
                text = "Linux/UVC"
            else:
                text = "Windows/Proprietary"
            self.confirmation_mode_label.set_text("Mode will be changed to \"{0}\""
                                                  .format(text))
        else:
            self.confirmation_mode_label.set_text("")

    def create_progression_page(self):
        """

        """
        vbox = Gtk.VBox()
        vbox.set_border_width(5)
        self.assistant.append_page(vbox)
        self.assistant.set_page_title(vbox, "Updating")
        self.assistant.set_page_type(vbox, Gtk.AssistantPageType.PROGRESS)
        label = Gtk.Label("Please wait while your changes are being applied ")
        label.set_line_wrap(True)
        vbox.pack_start(label, True, False, 0)
        self.progressbar = Gtk.ProgressBar()

        vbox.pack_start(self.progressbar, False, True, 0)

    def create_last_page(self):
        """
        Display status of all operations.
        """
        vbox = Gtk.VBox()
        vbox.set_border_width(5)
        self.assistant.append_page(vbox)
        self.assistant.set_page_title(vbox, "Result")

        self.assistant.set_page_type(vbox, Gtk.AssistantPageType.SUMMARY)
        self.result_label = Gtk.Label("")
        self.result_label.set_line_wrap(True)

        vbox.add(self.result_label)
        self.assistant.set_page_complete(vbox, True)

    def show_file_dialog(self, widget):
        """
        Shows a filechooser dialog to select firmware
        saves filename and sets entry text
        """
        filechooser = Gtk.FileChooserDialog(title="Open file",
                                            action=Gtk.FileChooserAction.OPEN,
                                            buttons=["Open",
                                                     Gtk.ResponseType.OK,
                                                     "Cancel",
                                                     Gtk.ResponseType.CANCEL])
        filter_py = Gtk.FileFilter()
        filter_py.set_name("Firmware files")
        filter_py.add_pattern("*.euvc")
        filechooser.add_filter(filter_py)

        filter_any = Gtk.FileFilter()
        filter_any.set_name("Any files")
        filter_any.add_pattern("*")
        filechooser.add_filter(filter_any)
        filechooser.run()
        self.filename = filechooser.get_filename()
        self.entry.set_text(self.filename)
        filechooser.destroy()

    def camera_selected(self, selection):
        """
        Callback method for camera selection
        """
        page_number = self.assistant.get_current_page()
        current_page = self.assistant.get_nth_page(page_number)
        model, treeiter = selection.get_selected()
        if treeiter is not None:
            print model[treeiter][0]
            for c in self.cameras:
                if c.serial == model[treeiter][1]:
                    self.selected_camera = c
                    self.assistant.set_page_complete(current_page, True)
        else:
            self.assistant.set_page_complete(current_page, False)

    def button_pressed(self, assistant, button):
        if button == "Cancel":
            Gtk.main_quit()
        if button == "Close":
            Gtk.main_quit()

    def workflow_selection(self, widget, data=Workflow.ALL):
        """
        """
        if data == Workflow.MODE or data == Workflow.ALL:
            self.workflow = data
            if data == Workflow.ALL:
                self.change_firmware = True
            else:
                self.change_firmware = False

    def fw_selection(self, widget, data=None):
        """
        """
        page_number = self.assistant.get_current_page()
        current_page = self.assistant.get_nth_page(page_number)
        if data == "manual":
            self.file_button.set_sensitive(True)
            self.entry.set_sensitive(True)
            self.assistant.set_page_complete(current_page, True)
        else:
            self.file_button.set_sensitive(False)
            self.entry.set_sensitive(False)
            if self.entry.get_text == "":
                self.assistant.set_page_complete(current_page, True)
            else:
                self.assistant.set_page_complete(current_page, True)

    def mode_selection(self, widget, data=CameraMode.PROPRIETARY):
        """
        """
        print "Selected ", data
        if data == CameraMode.PROPRIETARY:
            self.selected_mode = CameraMode.PROPRIETARY  # windows compability
        else:
            self.selected_mode = CameraMode.UVC  # linux compability

    def run_external_process(self, exe):
        """
        Runs the specified command as a subprocess
        and returns the complete output as a string list

        Keyword arguments:
        exe - list of a shell command and its parameters
        """
        p = subprocess.Popen(exe,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT)

        list = []
        for i in iter(p.stdout.readline, b''):
            list.append(i)
        return list

    def retrieve_cameras(self):

        """
        Retrieves the cameras

        Returns a list of found cameras
        """
        cam_list = []

        output = self.run_external_process([EUVCCAM, "-p"])

        for i in range(len(output)):
            if "Product String" in output[i]:
                # found new camera
                name = output[i][(output[i].find(": ") + 2)::]
                serial = output[i+1][(output[i+1].find(": ") + 2)::]
                pid = output[i+2][(output[i+2].find(": ") + 2)::]
                flags = output[i+3][(output[i+3].find(": ") + 2)::]

                c = Camera(_productstring=name, _serial=serial,
                           _pid=pid, _flags=flags)
                cam_list.append(c)

        return cam_list

    def write_firmware(self, firmware=None, callback=None):
        """"""
        if firmware is None:
            output = self.run_external_process([EUVCCAM, "-u"])
        else:
            self.run_external_process([EUVCCAM, "-u ", firmware])

        return True

    def toggle_camera_mode(self, mode=CameraMode.PROPRIETARY):
        """"""
        if mode == CameraMode.PROPRIETARY:
            self.run_external_process([EUVCCAM, "-m uvc"])
        else:
            self.run_external_process([EUVCCAM, "-m proprietary"])

        output = self.run_external_process([EUVCCAM, "-p"])
        for s in output:
            if "Flags:  1" in s:
                # success
                return True
        # something went wrong
        return True

if __name__ == "__main__":

    if not search_euvccam():
        sys.stderr.write("Unable to locate euvccam-fw in PATH.\n")
        exit(1)

    GObject.threads_init()
    a = Assistant()
    a.start()
    Gtk.main()
